Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
@poppinss/manager
Advanced tools
Abstract class to incapsulate the behavior of constructing and re-using named objects.
The module is used heavily by AdonisJs to build it's driver based features for components like hash
, mail
, auth
, social auth
and so on.
If you are a user of AdonisJs, then you will be familiar with the following API.
mail.use('smtp').send()
// or
hash.use('argon2').hash('')
The use
method here constructs the driver for the mapping defined in the config file with the ability to cache the constructed drivers and add new drivers using the extend
API.
Install the package from npm registry as follows:
npm i @poppinss/manager
# yarn
yarn add @poppinss/manager
Let's imagine we are building a mailer (with dummy implementation) that supports multiple drivers and each driver can be used for multiple times. Example config for same
{
mailer: 'transactional',
mailers: {
transactional: {
👇 // driver is important
driver: 'smtp',
host: '',
user: '',
password: ''
},
promotional: {
driver: 'mailchimp',
apiKey: ''
}
}
}
Our module supports two drivers smtp
and mailchimp
and here's how we can construct them.
class SmtpDriver {
constructor (config) {}
send () {}
}
class MailchimpDriver {
constructor (config) {}
send () {}
}
The consumer of our code can import and use these drivers manually by constructing a new instance every time. However, we can improve the developer experience, by creating the following manager class.
import { Manager } from '@poppinss/manager'
class Mailer extends Manager {
constructor (container, private config) {
super(container)
}
protected $cacheMappings = true
protected getDefaultMappingName (): string {
return this.config.mailer
}
protected getMappingConfig (name): any {
return this.config.mailers[name]
}
protected getMappingDriver (name): any {
return this.config.mailers[name].driver
}
protected createSmtp (name, config) {
return new Smtp(config)
}
protected createMailchimp (name, config) {
return new MailchimpDriver(config)
}
}
The Manager
class forces the parent class to define some of the required methods/properties.
mailer
set in the configmailers
object defined in the config.The other two methods createSmtp
and createMailchimp
are the methods invoked when the end user will access a driver. This is how it works.
const mailer = new Mailer({}, config)
mailer.use('smtp').send()
The mailer.use('transactional')
will invoke createTransactional
as part of the following convention.
create
+ PascalCaseDriverName
create
+ Smtp
= createSmtp
create
+ Mailchimp
= createMailchimp
NOTE: You need one method for each driver and not the mapping. The mapping names can be anything the user wants to keep in their config file.
The release
method can be used release the mappings from cache.
// Removes smtp from cache. Next call to `use('smtp')` will create a new instance
mailer.release('smtp')
The manager class also exposes the API to add new drivers from outside in and this is done using the extend
method.
mailer.extend('postmark', (container, name, config) => {
return new Postmark(config)
})
The Manager
class needs a container value, which is then passed to the extended
drivers. This can be anything from application state to an empty object.
In case of AdonisJs, it is the instance of the IoC container, so that the outside world (extended drivers) can pull dependencies from the container.
In order for intellisense to work, you have to do some ground work of defining additional types. This in-fact is a common theme with static languages, that you have to rely on loose coupling when creating or using extensible objects.
The first thing you must have in place is the interface that every driver adheres to.
interface MailDriverContract {
send (): void
}
And then pass it as a generic to the Manager
constructor
class Mailer extends Manager<MailDriverContract> {
// rest of the code
}
Now, extend
method will ensure that all drivers adhere to the MailDriverContract
.
use
method intellisenseCurrently the output of use
method will be typed to MailDriverContract
and not the actual implementation class. This is fine, when all drivers have the same properties, methods and output. However, you can define a list of mappings as follows
type MappingsList = {
transactional: SmtpDriver,
promotional: MailchimpDriver,
}
And then pass this mapping as a 2nd argument to the Manager
constructor.
class Mailer extends Manager<
MailDriverContract,
MailDriverContract,
MappingsList
> {
}
The use
method will now return the concrete type. You can also use this same mapping to enforce correct configuration.
type SmtpConfig = {
driver: 'smtp',
host: string,
user: string,
password: string,
port?: number,
}
type MailchimpConfig = {
driver: 'mailchimp',
apiKey: string,
}
type MappingsList = {
transactional: {
config: SmtpConfig,
implementation: SmtpDriver,
},
promotional: {
config: MailchimpConfig,
implementation: MailchimpDriver,
},
}
The master MappingsList
will ensure that static types and the configuration is always referring to the same driver instance.
import { ExtractConfig } from '@poppinss/manager'
type Config<Mailer extends keyof MappingsList> = {
mailer: Mailer,
mailers: ExtractConfig<MappingsList>,
}
const config: Config<'transactional'> = {
mailer: 'transactional',
mailers: {
transactional: {
driver: 'smtp',
host: '',
user: '',
password: '',
},
promotional: {
driver: 'mailchimp',
apiKey: '',
},
},
}
import { ExtractImplementations, Manager } from '@poppinss/manager'
class Mailer extends Manager<
MailDriverContract,
MailDriverContract,
ExtractImplementations<MappingsList>
> {
}
Finally, you have runtime functionality to switch between multiple mailers and also have type safety for same.
FAQs
The builder (Manager) pattern implementation
The npm package @poppinss/manager receives a total of 3,199 weekly downloads. As such, @poppinss/manager popularity was classified as popular.
We found that @poppinss/manager demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.